# -*- coding: utf-8 -*-
"""
UTc! module: src.rendering.rendering

Purpose
=======
 Provides generic routines for rendering game elements.
 
Legal
=====
 All code, unless otherwise indicated, is original, and subject to the
 terms of the GPLv2, which is provided in COPYING.
 
 (C) Neil Tallim, 2009
"""
import pygame
import OpenGL.GL as GL
import OpenGL.GLU as GLU

import src.matrix as matrix

UNIT_QUAD = None #: The GL List ID of the unit quad, a 1x1 quad that accepts a texture.
UNIT_CUBE = None #: The GL List ID of the unit cube, a 1x1x1 cube with normals.

def buildTexture(pygame_texture):
	"""
	Converts a PyGame Surface into an OpenGL texture.
	
	@type pygame_texture: pygame.Surface
	@param pygame_texture: The Surface to convert into an OpenGL texture.
	
	@rtype: int
	@return: An OpenGL texture ID.
	"""
	width = pygame_texture.get_width()
	height = pygame_texture.get_height()
	pygame_texture = pygame.image.tostring(pygame_texture, "RGBA", 1)
	
	texture_id = GL.glGenTextures(1)
	GL.glBindTexture(GL.GL_TEXTURE_2D, texture_id)
	GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
	GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
	GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pygame_texture)
	return texture_id
	
def init():
	"""
	Builds the unit quad and unit cube, populating their List IDs and making this
	module usable.
	"""
	#Set up the generic unit-quad.
	global UNIT_QUAD
	UNIT_QUAD = GL.glGenLists(1)
	GL.glNewList(UNIT_QUAD, GL.GL_COMPILE)
	GL.glFrontFace(GL.GL_CCW)
	GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
	GL.glBegin(GL.GL_QUADS)
	GL.glTexCoord2f(0, 0)
	GL.glVertex3f(-0.5, -0.5, 0)
	GL.glTexCoord2f(1, 0)
	GL.glVertex3f(0.5, -0.5, 0)
	GL.glTexCoord2f(1, 1)
	GL.glVertex3f(0.5, 0.5, 0)
	GL.glTexCoord2f(0, 1)
	GL.glVertex3f(-0.5, 0.5, 0)
	GL.glEnd()
	GL.glEndList()
	
	#Set up the generic unit-cube.
	global UNIT_CUBE
	UNIT_CUBE = GL.glGenLists(1)
	GL.glNewList(UNIT_CUBE, GL.GL_COMPILE)
	GL.glFrontFace(GL.GL_CCW)
	GL.glBegin(GL.GL_QUADS)
	GL.glNormal(0, 1, 0)
	GL.glVertex3f(0.5, 0.5, -0.5)
	GL.glVertex3f(-0.5, 0.5, -0.5)
	GL.glVertex3f(-0.5, 0.5, 0.5)
	GL.glVertex3f(0.5, 0.5, 0.5)
	
	GL.glNormal(0, -1, 0)
	GL.glVertex3f(0.5, -0.5, 0.5)
	GL.glVertex3f(-0.5, -0.5, 0.5)
	GL.glVertex3f(-0.5, -0.5, -0.5)
	GL.glVertex3f(0.5, -0.5, -0.5)
	
	GL.glNormal(0, 0, 1)	
	GL.glVertex3f(0.5, 0.5, 0.5)
	GL.glVertex3f(-0.5, 0.5, 0.5)
	GL.glVertex3f(-0.5, -0.5, 0.5)
	GL.glVertex3f(0.5, -0.5, 0.5)
	
	GL.glNormal(0, 0, -1)
	GL.glVertex3f(0.5, -0.5, -0.5)
	GL.glVertex3f(-0.5, -0.5, -0.5)
	GL.glVertex3f(-0.5, 0.5, -0.5)
	GL.glVertex3f(0.5, 0.5, -0.5)
	
	GL.glNormal(-1, 0, 0)
	GL.glVertex3f(-0.5, 0.5, 0.5)
	GL.glVertex3f(-0.5, 0.5, -0.5)
	GL.glVertex3f(-0.5, -0.5, -0.5)
	GL.glVertex3f(-0.5, -0.5, 0.5)
	
	GL.glNormal(1, 0, 0)
	GL.glVertex3f(0.5, 0.5, -0.5)
	GL.glVertex3f(0.5, 0.5, 0.5)
	GL.glVertex3f(0.5, -0.5, 0.5)
	GL.glVertex3f(0.5, -0.5, -0.5)
	GL.glEnd()
	GL.glEndList()
	
def renderCone(position, rotation_matrix, radius, height, colour):
	"""
	Renders a cone.
	
	@type position: sequence(3)
	@param position: The (x, y, z) co-ordinates at which the cone is to be
	    rendered.
	@type rotation_matrix: sequence(9)
	@param rotation_matrix: The rotation to apply to this cone.
	@type radius: float
	@param radius: The size of the base of this cone.
	@type height: float
	@param height: The length of this cone.
	@type colour: sequence(3)
	@param colour: The colour to use when rendering this cone, in percentage
	    form.
	"""
	_setRenderProperties(position, rotation_matrix, colour)
	quadric = GLU.gluNewQuadric()
	GLU.gluCylinder(quadric, radius, 0.0, height, 8, 1)
	GLU.gluDeleteQuadric(quadric)
	
def renderCylinder(position, rotation_matrix, radius, height, colour):
	"""
	Renders a cylinder.
	
	@type position: sequence(3)
	@param position: The (x, y, z) co-ordinates at which the cylinder is to be
	    rendered.
	@type rotation_matrix: sequence(9)
	@param rotation_matrix: The rotation to apply to this cylinder.
	@type radius: float
	@param radius: The size of the ends of this cylinder.
	@type height: float
	@param height: The length of this cylinder.
	@type colour: sequence(3)
	@param colour: The colour to use when rendering this cylinder, in percentage
	    form.
	"""
	_setRenderProperties(position, rotation_matrix, colour)
	quadric = GLU.gluNewQuadric()
	GLU.gluCylinder(quadric, radius, radius, height, 6, 1)
	GLU.gluDeleteQuadric(quadric)
	
def renderCube(position, rotation_matrix, lengths, colour):
	"""
	Renders a cube.
	
	@type position: sequence(3)
	@param position: The (x, y, z) co-ordinates at which the cube is to be
	    rendered.
	@type rotation_matrix: sequence(9)
	@param rotation_matrix: The rotation to apply to this cube.
	@type lengths: sequence(3)
	@param lengths: The (x, y, z) lengths to use when scaling the cube.
	@type colour: sequence(3)
	@param colour: The colour to use when rendering this cube, in percentage
	    form.
	"""
	_setRenderProperties(position, rotation_matrix, colour)
	GL.glScale(*lengths)
	GL.glCallList(UNIT_CUBE)
	
def renderOrtho(x, y, z, scale, texture, unit_scale=False):
	"""
	Renders a quad that faces the camera using sphere billboarding.
	
	@type x: float
	@param x: The X-co-ordinate at which rendering should be centred.
	@type y: float
	@param y: The Y-co-ordinate at which rendering should be centred.
	@type z: float
	@param z: The Z-co-ordinate at which rendering should be centred.
	@type scale: float
	@param scale: The size of the quad that should be rendered.
	@type texture: int
	@param texture: The OpenGL texture ID to map to the cube.
	@type unit_scale: bool
	@param unit_scale: If True, then the relative Z-distance of the quad will be
	    used to scale the quad so its size appears to be constant.
	"""
	GL.glPushMatrix()
	
	GL.glTranslate(x, y, z)
	transform = GL.glGetFloatv(GL.GL_MODELVIEW_MATRIX)
	transform = [i for i in transform[0]] + [i for i in transform[1]] + [i for i in transform[2]] + [i for i in transform[3]]
	transform[0] = transform[5] = transform[10] = 1.0
	transform[1] = transform[2] = transform[4] = transform[6] = transform[8] = transform[9] = 0.0
	GL.glLoadMatrixf(transform)
	if unit_scale:
		scale *= abs(transform[14])
	GL.glScale(scale, scale, 0)
	
	dynamic_texture = type(texture) is pygame.Surface
	if dynamic_texture:
		texture_id = buildTexture(texture)
	else:
		texture_id = texture
		
	GL.glBindTexture(GL.GL_TEXTURE_2D, texture_id)
	GL.glCallList(UNIT_QUAD)
	
	if dynamic_texture:
		GL.glDeleteTextures([texture_id])
		
	GL.glPopMatrix()
	
def renderSphere(position, rotation_matrix, radius, slices, stacks, colour):
	"""
	Renders a sphere.
	
	@type position: sequence(3)
	@param position: The (x, y, z) co-ordinates at which the sphere is to be
	    rendered.
	@type rotation_matrix: sequence(9)
	@param rotation_matrix: The rotation to apply to this sphere.
	@type radius: float
	@param radius: The size of this sphere.
	@type slices: int
	@param slices: The number of longitudinal lines by which this sphere should
	    be divided.
	@type stacks: int
	@param stacks: The number of latitudinal lines by which this sphere should be
	    divided.
	@type colour: sequence(3)
	@param colour: The colour to use when rendering this sphere, in percentage
	    form.
	"""
	_setRenderProperties(position, rotation_matrix, colour)
	quadric = GLU.gluNewQuadric()
	GLU.gluSphere(quadric, radius, slices, stacks)
	GLU.gluDeleteQuadric(quadric)
	
def _setRenderProperties(position, rotation_matrix, colour):
	"""
	Sets OpenGL rendering paramters.
	
	@type position: sequence(3)
	@param position: The (x, y, z) co-ordinates at which rendered co-ordinates
	    should be centred.
	@type rotation_matrix: sequence(9)
	@param rotation_matrix: The rotation to apply when rendering.
	@type colour: sequence(3)
	@param colour: The colour to use when rendering, in percentage form.
	"""
	if rotation_matrix:
		GL.glMultMatrixf(matrix.buildRenderMatrix(rotation_matrix, position))
	else:
		GL.glTranslate(*position)
	GL.glColor(*colour)
	